package calao.client.ui;

import java.util.Date;
import java.util.Vector;

import calao.client.util.CalendarDate;

import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.SourcesTableEvents;
import com.google.gwt.user.client.ui.TableListener;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.Grid;

/** 
 *
 * Thanks to Nicolas Wetzel and Jean-Philippe Dournel, this calendar was inspired by their 
 * work on a similar widget.
 */
public class Calendar 
extends Composite
{

	final static String STYLE = "calao-Calendar";

	final static String DAY_STYLE = "day";
	final static String MOVE_STYLE = "move";
	final static String MONTH_LABEL_STYLE = "monthLabel";
	final static String MONTH_BAR_STYLE = "monthBar";
	final static String DAYS_GRID_STYLE = "daysGrid";
	final static String WEEKDAY_STYLE = "weekDay";
	final static String OUT_STYLE = "out";

	final static DateTimeFormat monthFormat = DateTimeFormat.getFormat("MMMM yyyy");
	final static DateTimeFormat dayFormat = DateTimeFormat.getFormat("d");
	final static DateTimeFormat weekDayFormat = DateTimeFormat.getFormat("EEE");

	class MonthBar extends HorizontalPanel 
	{
		Label shownMonthLabel = new Label();
		MonthBar() 
		{
			Label prevYear = new Label("«");
			prevYear.addClickListener(new ClickListener() {
				public void onClick(Widget sender) {
					shownMonth.incYear(-1);
					Calendar.this.refresh();
				}
			});
			Label nextYear = new Label("»");
			nextYear.addClickListener(new ClickListener() {
				public void onClick(Widget sender) {
					shownMonth.incYear(1);
					Calendar.this.refresh();
				}
			});
			Label prevMonth = new Label("‹");
			prevMonth.addClickListener(new ClickListener() {
				public void onClick(Widget sender) {
					shownMonth.incMonth(-1);
					Calendar.this.refresh();
				}
			});
			Label nextMonth = new Label("›");
			nextMonth.addClickListener(new ClickListener() {
				public void onClick(Widget sender) {
					shownMonth.incMonth(1);
					Calendar.this.refresh();
				}
			});
			add(prevYear);
			add(prevMonth);
			add(shownMonthLabel);
			add(nextMonth);
			add(nextYear);		
			prevYear.addStyleName(MOVE_STYLE);
			nextYear.addStyleName(MOVE_STYLE);
			prevMonth.addStyleName(MOVE_STYLE);
			nextMonth.addStyleName(MOVE_STYLE);
			shownMonthLabel.addStyleName(MONTH_LABEL_STYLE);
			addStyleName(MONTH_BAR_STYLE);
		}
		void refresh()
		{
			shownMonthLabel.setText(monthFormat.format(shownMonth.getTime()));
		}
	}

	class DaysGrid extends Grid 
	implements TableListener
	{
		private Date firstShownDay;
		private int selectedWeek, selectedDay = -1;
		DaysGrid() 
		{
			addTableListener(this);
			addStyleName(DAYS_GRID_STYLE);
			this.setCellSpacing(0);
		}
		public void resize(int rows, int columns) {
			if (numRows!=rows || numColumns!=columns) {
				super.resize(rows, columns);
				CellFormatter formatter = getCellFormatter();
				for (int row=0; row<numRows; row++) {
					for (int col=0; col<numColumns; col++) {
						if (row==0) formatter.addStyleName(row, col, WEEKDAY_STYLE);
						else formatter.addStyleName(row, col, DAY_STYLE);
					}
				}			
				Calendar.this.setWidth(""+(24*numColumns+4)+"px");
			}
		}
		public void onCellClicked(SourcesTableEvents sender, int row, int col) {
			if (row==0) return;
			CalendarDate cal = new CalendarDate(firstShownDay);
			cal.incDay((row-1)*7+col);
			if (cal.getMonth()==shownMonth.getMonth()) {
				hideSelected();
				selectedDate = cal.getTime();
				getCellFormatter().addStyleName(row, col, Styles.SELECTED);
				selectedWeek= row;
				selectedDay = col;
				notifyDateSelected();
				showSelected();
			}
		}
		void refresh()
		{
			CellFormatter formatter = getCellFormatter();
			resize(7, weekdays.length);
			hideSelected();
			selectedDay = -1;
			CalendarDate scal = selectedDate!=null? new CalendarDate(selectedDate) : null;
			CalendarDate cal = new CalendarDate(shownMonth.getYear(), shownMonth.getMonth(), 1);
			// start with the first day of the month in visible weekdays
			cal.incDay(weekdays[0]-cal.getWeekDay());
			if (cal.getMonth()!=shownMonth.getMonth()) {
				CalendarDate lastWeekday = cal.copyWithOffset(0, 0, weekdays.length-1);
				if (lastWeekday.getMonth()!=shownMonth.getMonth()) {
					cal.incDay(7);
				}
			}
			firstShownDay = cal.getTime();
			for (int week=1; week<numRows; week++) {
				for (int day=0; day<7; day++) {
					if (day<weekdays.length) {
						if (cal.getMonth()==shownMonth.getMonth()) {
							formatter.removeStyleName(week, day, OUT_STYLE);
						}
						else {
							formatter.addStyleName(week, day, OUT_STYLE);
						}
						if (week==1) {
							daysGrid.setText(0, day, weekDayFormat.format(cal.getTime()));
						}
						daysGrid.setText(week, day, dayFormat.format(cal.getTime()));
						if (scal!=null && cal.compareDateTo(scal)==0) {
							selectedWeek = week;
							selectedDay = day;
						}
					}
					cal.incDay(1);
				}
			}
			showSelected();
		}
		void hideSelected() 
		{
			if (selectedDay!=-1) getCellFormatter().removeStyleName(
					selectedWeek, selectedDay, Styles.SELECTED);
		}
		void showSelected() 
		{
			if (selectedDay!=-1) getCellFormatter().addStyleName(
					selectedWeek, selectedDay, Styles.SELECTED);
		}
	}

	final Vector listeners = new Vector();
	final CalendarDate shownMonth;
	final MonthBar monthBar;
	final DaysGrid daysGrid;

	// maps days grid columns to weekdays
	int[] weekdays = new int[] { CalendarDate.SUNDAY, CalendarDate.MONDAY, 
			CalendarDate.TUESDAY, CalendarDate.WEDNESDAY, CalendarDate.THURSDAY, 
			CalendarDate.FRIDAY, CalendarDate.SATURDAY
	};

	Date selectedDate;
	boolean autoRefresh;

	public Calendar()
	{
		this(new Date(), true);
	}

	Calendar(Date selectedDate)
	{
		this(selectedDate, true);
	}

	Calendar(Date selectedDate, boolean autoRefresh)
	{
		this.selectedDate = selectedDate;	
		this.autoRefresh = autoRefresh;
		shownMonth = selectedDate!=null? new CalendarDate(selectedDate) : new CalendarDate();
		VerticalPanel panel = new VerticalPanel();
		monthBar = new MonthBar();
		daysGrid = new DaysGrid();
		panel.add(monthBar);
		panel.add(daysGrid);
		panel.addStyleName(STYLE);
		initWidget(panel);
		if (autoRefresh) refresh();
	}

	public void setSelectedDate(Date date)
	{
		selectedDate = date;
		if (autoRefresh) refresh();
	}

	public Date getSelectedDate()
	{
		return selectedDate;
	}

	public void refresh()
	{
		monthBar.refresh();
		daysGrid.refresh();
	}

	/** Moves the calendar to the selected date's month.
	 */
	public void showSelectedDate()
	{
		if (selectedDate!=null) {
			CalendarDate scal = new CalendarDate(selectedDate);
			if (scal.compareMonthTo(shownMonth)!=0) {
				shownMonth.setTime(selectedDate);
			}
		}
		else {
			shownMonth.setTime(new Date());
		}	
		if (autoRefresh) refresh();
	}

	public void addListener(CalendarListener listener)
	{
		listeners.add(listener);
	}

	private void notifyDateSelected()
	{
		for (int i=0; i<listeners.size(); i++) {
			((CalendarListener)listeners.get(i)).onSelect(selectedDate);
		}
	}

	public void hideWeekends()
	{
		hideWeekday(CalendarDate.SUNDAY);
		hideWeekday(CalendarDate.SATURDAY);
		if (autoRefresh) refresh();
	}

	private void hideWeekday(int weekday) 
	{
		for (int i=0; i<weekdays.length; i++) {
			if (weekdays[i]==weekday) {
				removeWeekday(i);
				break;
			}
		}		
	}

	private void removeWeekday(int index)
	{
		int[] newWeekdays = new int[weekdays.length-1];
		int count = 0;
		for (int i=0; i<weekdays.length; i++) {
			if (i!=index) newWeekdays[count++] = weekdays[i];
		}		
		weekdays = newWeekdays;
	}

}
